package com.google.code.stalkernet.async.io;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.channels.NetworkChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

import com.google.code.stalkernet.async.Async;
import com.google.code.stalkernet.reactor.Reactor;

/**
 * Asynchronous wrapper for {@link ServerSocketChannel}.
 *
 * @author Mikhail Vladimirov
 */
public class AsyncServerSocketChannel
    extends AbstractAsyncSelectableChannel
    implements Async, NetworkChannel
{
    private final ServerSocketChannel serverSocketChannel;

    private Runnable pendingAccept = null;

    /**
     * Create new asynchronous server socket channel wrapping given
     * {@link ServerSocketChannel}.
     *
     * @param serverSocketChannel {@link ServerSocketChannel} to wrap
     * @param reactor {@link Reactor} to use
     * @throws IOException if I/O error occurred
     */
    public AsyncServerSocketChannel (
        ServerSocketChannel serverSocketChannel, Reactor reactor)
        throws IOException
    {
        super (serverSocketChannel, reactor);

        if (serverSocketChannel == null)
            throw new IllegalArgumentException (
                "Server socket channel is null");

        this.serverSocketChannel = serverSocketChannel;
    }

    /**
     * Open new asynchronous server socket channel.
     *
     * @param reactor Reactor to use
     * @return {@link AsyncServerSocketChannel} object
     * @throws IOException if I/O error occurred
     */
    public static AsyncServerSocketChannel open (Reactor reactor)
        throws IOException {
        if (reactor == null)
            throw new IllegalArgumentException ("Reactor is null");

        return new AsyncServerSocketChannel (
            ServerSocketChannel.open (), reactor);
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public boolean isOpen ()
    {
        return serverSocketChannel.isOpen ();
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public void close () throws IOException
    {
        serverSocketChannel.close ();
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public AsyncServerSocketChannel bind(SocketAddress local)
        throws IOException
    {
        serverSocketChannel.bind (local);

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public SocketAddress getLocalAddress () throws IOException
    {
        return serverSocketChannel.getLocalAddress ();
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public <T> NetworkChannel setOption (SocketOption <T> name, T value)
        throws IOException
    {
        serverSocketChannel.setOption (name, value);

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public <T> T getOption (SocketOption <T> name) throws IOException
    {
        return serverSocketChannel.getOption (name);
    }

    /**
     * {@inheritDoc}
     */
    @Override // NetworkChannel
    public Set <SocketOption <?>> supportedOptions ()
    {
        return serverSocketChannel.supportedOptions ();
    }

    /**
     * Wrapper for {@link ServerSocketChannel#bind(SocketAddress, int)}.
     *
     * @param local The address to bind the socket, or {@code null} to bind to
     *        an automatically assigned socket address
     * @param backlog The maximum number of pending connections
     * @return this {@link AsyncServerSocketChannel} object
     * @throws IOException if I/O error occurred
     */
    public AsyncServerSocketChannel bind(SocketAddress local, int backlog)
        throws IOException
    {
        serverSocketChannel.bind (local, backlog);

        return this;
    }

    /**
     * Wrapper for {@link ServerSocketChannel#socket()}.
     *
     * @return A server socket associated with this channel
     */
    public ServerSocket socket ()
    {
        return serverSocketChannel.socket ();
    }

    /**
     * Asynchronous wrapper for {@link ServerSocketChannel#accept()}.
     *
     * @param callback callback to receive asynchronous operation result
     * @return asynchronous operation handle
     */
    public Handle accept (Callback <SocketChannel> callback)
    {
        if (callback == null)
            throw new IllegalArgumentException ("Callback is null");

        if (pendingAccept != null)
            throw new IllegalStateException ("Pending accept is not null");

        AsyncAccept asyncAccept = new AsyncAccept (callback);

        pendingAccept = asyncAccept;

        updateInterestOps ();

        return asyncAccept;
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected boolean wantAccept ()
    {
        return pendingAccept != null;
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected void canAccept ()
    {
        pendingAccept.run ();
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected boolean wantConnect ()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected void canConnect ()
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected boolean wantRead ()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected void canRead ()
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected boolean wantWrite ()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override // AbstractAsyncSelectableChannel
    protected void canWrite ()
    {
        // Do nothing
    }

    private class AsyncAccept implements Runnable, Handle
    {
        private final Callback <SocketChannel> callback;

        public AsyncAccept (Callback <SocketChannel> callback)
        {
            if (callback == null)
                throw new IllegalArgumentException ("Callback is null");

            this.callback = callback;
        }

        @Override
        public void run ()
        {
            try
            {
                SocketChannel socketChannel = serverSocketChannel.accept ();
                if (socketChannel != null)
                {
                    pendingAccept = null;
                    callback.onSuccess (socketChannel);
                }
            }
            catch (IOException ex)
            {
                pendingAccept = null;
                callback.onError (ex);
            }
        }

        @Override
        public boolean cancel ()
        {
            if (pendingAccept == this)
            {
                pendingAccept = null;
                updateInterestOps ();
                return true;
            }
            return false;
        }
    }
}
